package com.briup.server.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.briup.common.constant.SystemConstant;
import com.briup.common.response.ResultCode;
import com.briup.common.utils.AssertUtil;
import com.briup.common.utils.BeanUtil;
import com.briup.common.utils.JwtUtil;
import com.briup.server.config.MenuPermissionMerger;
import com.briup.server.domain.bean.User;
import com.briup.server.domain.bean.UserRole;
import com.briup.server.domain.dto.*;
import com.briup.server.domain.vo.MenuVO;
import com.briup.server.domain.vo.RoleDetailVO;
import com.briup.server.domain.vo.RoleVO;
import com.briup.server.domain.vo.UserDetailVO;
import com.briup.server.mapper.UserMapper;
import com.briup.server.mapper.UserRoleMapper;
import com.briup.server.security.SecurityContext;
import com.briup.server.service.KaptchaService;
import com.briup.server.service.RoleService;
import com.briup.server.service.UserRoleService;
import com.briup.server.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    private final UserRoleMapper userRoleMapper;
    private final KaptchaService kaptchaService;
    private final RoleService roleService;
    private final UserRoleService userRoleService;
    private final RedisTemplate<String, Object> redisTemplate;

    @Override
    public String login(UserLoginDTO userLoginDTO) {
        // 1. 验证验证码是否可用
        KaptchaVerifyRequest request = KaptchaVerifyRequest
                .builder()
                .formId(userLoginDTO.getFormId())
                .kaptcha(userLoginDTO.getKaptchaCode())
                .build();
        kaptchaService.verifyCaptcha(request);

        // 2. 验证用户名和密码是否正确
        User user = baseMapper.selectOne(Wrappers.<User>lambdaQuery()
                .eq(User::getUsername, userLoginDTO.getUsername()));
        AssertUtil.notNull(user, ResultCode.USER_IS_NOT_EXISTS);
        // 验证密码是否正确
        AssertUtil.isTrue(user.getPassword().equals(userLoginDTO.getPassword()),
                ResultCode.PASSWORD_IS_ERROR);
        // 验证用户登录状态，确保用户状态正常，返回token
        return verifyUserLogin(user);
    }

    @Override
    public UserDetailVO queryByUserId(Long userId) {
        // 根据id查询用户基础信息，确保用户存在并且状态可用
        User user = getOne(Wrappers.<User>lambdaQuery().eq(User::getId, userId)
                .eq(User::getStatus, SystemConstant.StatusConstant.NORMAL.getStatusId()));
        // 验证用户是否存在
        AssertUtil.notNull(user, ResultCode.USER_IS_NOT_EXISTS);

        UserDetailVO userDetailVO = BeanUtil.convert(user, UserDetailVO.class);
        // 查询用户角色信息
        List<Long> roleIds = userRoleMapper.selectList(Wrappers.<UserRole>lambdaQuery()
                        .eq(UserRole::getUserId, userId)).stream()
                .map(UserRole::getRoleId)
                .toList();

        // 根据角色id列表查询角色信息
        List<RoleDetailVO> roles = roleService.queryByIds(roleIds);
        // 转换为vo对象
        List<RoleVO> roleVOS = BeanUtil.convertList(roles, RoleVO.class);
        userDetailVO.setRoles(roleVOS);
        // 将所有角色的菜单权限合并到一个列表中
        List<MenuVO> menus = roles.stream()
                // 合并所有角色的菜单权限，扁平化处理
                .flatMap(role -> role.getMenuList().stream())
                // 去重，避免重复菜单
                .distinct()
                .toList();
        menus = MenuPermissionMerger.mergeAllLevels(menus);
        userDetailVO.setMenus(menus);

        return userDetailVO;
    }

    @Override
    public Page<UserDetailVO> pageQueryByCondition(UserQueryDTO queryDTO) {
        // 创建分页对象
        Page<User> userPage = new Page<>(queryDTO.getPage(), queryDTO.getSize());
        // 构建分页查询条件构造器
        LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
                // 当用户名不为空时，添加模糊查询条件
                .like(StringUtils.hasText(queryDTO.getUsername()),
                        User::getUsername, queryDTO.getUsername());
        // 执行分页查询
        baseMapper.selectPage(userPage, wrapper);

        // 将原有的分页数据，转换为存储UserDetailVO的分页对象
        Page<UserDetailVO> convertPage = BeanUtil.convertPage(userPage, UserDetailVO.class);
        // 为每个用户详情对象设置角色信息
        convertPage.getRecords().forEach(userDetailVO -> {
            // 根据用户id查询角色信息
            List<Long> roleIds = userRoleService.list(Wrappers.<UserRole>lambdaQuery()
                            .eq(UserRole::getUserId, userDetailVO.getId()))
                    .stream().map(UserRole::getRoleId)
                    .toList();
            if (!roleIds.isEmpty()) {
                // 根据角色id列表查询角色信息
                List<RoleDetailVO> roles = roleService.queryByIds(roleIds);
                // 转换为vo对象
                List<RoleVO> roleVOS = BeanUtil.convertList(roles, RoleVO.class);
                userDetailVO.setRoles(roleVOS);
            } else {
                userDetailVO.setRoles(List.of());
            }
        });
        // 设置分页对象的总记录数，应该是符合条件的用户总数
        convertPage.setTotal(count(wrapper));

        return convertPage;
    }

    @Override
    @Transactional
    public void saveOrUpdateUser(UserModifiedDTO modifiedDTO) {
        // 校验用户是否存在
        checkUserExists(modifiedDTO);
        // 将DTO转换为实体对象
        User user = BeanUtil.convert(modifiedDTO, User.class);
        // 新增用户时，设置默认值
        if (modifiedDTO.getId() == null) {
            // 新增用户时，设置默认值
            user.setStatus((byte) SystemConstant.StatusConstant.NORMAL.getStatusId());
            user.setPassword(SystemConstant.DEFAULT_PASSWORD);
            user.setCreateUserId(SecurityContext.userId());
            user.setCreateTime(LocalDateTime.now());
        }
        // 新增或更新用户信息，调用MyBatis-Plus的saveOrUpdate方法，
        // 它会自动根据实体对象的id是否为null来判断是新增还是更新操作
        // 新增用户后，mybatis-plus会在原有user对象中给id赋值，
        // 所以在新增用户后，user对象的id属性就不是null了
        saveOrUpdate(user);
        // 新增或更新用户角色关联关系
        userRoleService.grantRolesToUser(user.getId(), modifiedDTO.getRoleIds());
        // 新增或更新用户详情缓存
        ValueOperations<String, Object> operations = redisTemplate.opsForValue();
        // 只去更新在redis中存储了的数据，避免更新不存在的数据，同时同步更新数据库中的数据
        operations.setIfPresent("userdetail:" + user.getId(), queryByUserId(user.getId()));
    }

    @Override
    public void resetPassword(Long userId) {
        // 根据用户id查询用户是否存在
        User user = getById(userId);
        // 验证用户是否存在
        AssertUtil.notNull(user, ResultCode.USER_IS_NOT_EXISTS);
        // 重置密码为默认值
        user.setPassword(SystemConstant.DEFAULT_PASSWORD);
        // 更新用户信息
        updateById(user);
    }

    @Override
    public void updatePassword(UserResetPasswordDTO resetPasswordDTO) {
        // 根据用户id查询用户是否存在
        User user = getById(SecurityContext.userId());
        // 验证用户是否存在
        AssertUtil.notNull(user, ResultCode.USER_IS_NOT_EXISTS);
        // 验证旧密码是否正确
        AssertUtil.isTrue(user.getPassword().equals(resetPasswordDTO.getOldPassword()),
                ResultCode.OLD_PASSWORD_IS_ERROR);
        // 更新密码为新密码
        user.setPassword(resetPasswordDTO.getNewPassword());
        // 更新用户信息
        updateById(user);
    }

    @Transactional
    @Override
    public void removeUser(List<Long> ids) {
        // 先删除关联关系
        userRoleService.remove(Wrappers.<UserRole>lambdaQuery()
                .in(UserRole::getUserId, ids));
        // 在删除用户表中的数据
        removeByIds(ids);
        // 删除用户详情缓存，不用关心该数据是否存在
        redisTemplate.delete("userdetail:" + ids);
    }

    @Override
    public String loginByCode(UserLoginByCodeDTO userLoginByCodeDTO) {
        // 校验验证码是否正确
        KaptchaVerifyRequest kaptcha = KaptchaVerifyRequest.builder()
                .formId(userLoginByCodeDTO.getEmail())
                .kaptcha(userLoginByCodeDTO.getCode())
                .build();
        kaptchaService.verifyCaptcha(kaptcha);
        // 验证码验证通过后，根据邮箱查询用户是否存在
        User user = getOne(Wrappers.<User>lambdaQuery()
                .eq(User::getEmail, userLoginByCodeDTO.getEmail()));
        // 验证用户是否存在
        AssertUtil.notNull(user, ResultCode.USER_IS_NOT_EXISTS);
        // 验证用户登录状态，确保用户状态正常，返回token
        return verifyUserLogin(user);
    }

    /**
     * 验证用户登录状态，确保用户状态正常，返回token
     *
     * @param user 用户实体
     * @return 登录成功后的token
     */
    private String verifyUserLogin(User user) {
        // 1. 验证用户状态是否正常
        AssertUtil.isTrue(user.getStatus() == SystemConstant.StatusConstant.NORMAL.getStatusId(),
                ResultCode.USER_STATUS_IS_FORBIDDEN);

        // 2. 登录成功，返回token
        Map<String, Object> claims = Map.of("userId", user.getId(),
                "username", user.getUsername());

        // 3. 将当前用户的详细详细存储到redis中，key为userdetail:userId
        redisTemplate.opsForValue().set("userdetail:" + user.getId(),
                queryByUserId(user.getId()));

        return JwtUtil.sign(claims);
    }

    /**
     * 如果时修改操作，需要校验用户id是否存在，确保用户存在，才能进行修改操作；
     * 在修改和新增操作时，都需要校验用户名是否已存在，确保用户名唯一；
     *
     * @param modifiedDTO 用户修改DTO
     */
    private void checkUserExists(UserModifiedDTO modifiedDTO) {
        // 修改用户时，先根据用户id查询用户是否存在
        if (modifiedDTO.getId() != null) {
            // 根据用户id查询该用户是否存在
            User user = getById(modifiedDTO.getId());
            // 验证用户是否存在
            AssertUtil.notNull(user, ResultCode.USER_IS_NOT_EXISTS);
        }
        // 校验用户名是否存在
        User user = getOne(Wrappers.<User>lambdaQuery()
                .eq(User::getUsername, modifiedDTO.getUsername())
                .ne(modifiedDTO.getId() != null, User::getId, modifiedDTO.getId()));
        // 验证用户名是否已存在
        AssertUtil.isTrue(user == null, ResultCode.USER_IS_EXISTS);
    }

    @Override
    public List<User> labsAdminList() {
        // 查询所有实验室管理员对应的用户id
        List<Long> userIds = userRoleMapper.selectList(
                new LambdaQueryWrapper<UserRole>()
                        .eq(UserRole::getRoleId,
                                SystemConstant.RoleConstant.LAB_ADMIN.getRoleId())
                        .select(UserRole::getUserId)
        ).stream().map(UserRole::getUserId).toList();
        // 根据用户id查询实验室管理员用户列表
        return baseMapper.selectByIds(userIds);
    }
}




